package com.linrung.gdsfrz.electronicseal;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.map.MapBuilder;
import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.http.Method;
import cn.hutool.json.JSONUtil;
import com.linrung.core.model.ResultData;
import com.linrung.core.utils.SpringPropertyUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import java.io.File;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

/**
 * 新版电子印章API 使用国密加密
 * @see 《广东省统一电子印章平台接入指引（政府部门）v2.1.11V3.docx》
 * @author wurenlong
 */
public enum GDESealAPI {

    FILE_UPLOAD("/intercept/api/gdata/file/upload", "API文件上传", 30 * DateUnit.SECOND.getMillis()),

    FILE_DOWNLOAD("/intercept/core/downFile", "API文件下载", 30 * DateUnit.SECOND.getMillis()),
    ;

    private static final Logger LOGGER = Logger.getLogger(GDESealAPI.class);

    private static final String PC_SIGN_URL_TEMPLATE = "{host}/frontend-test/iviewer.html?docuri={docuri}&&siteId={siteId}&&typeId={typeId}&&type={type}&&documentName={documentName}&&timestamp={timestamp}&&features={features}&&appkey={appkey}&&random={random}&&digest={digest}";

    /**
     * 生成PC端调用的URL地址
     * @param docURI docUrl
     * @param userId userId
     * @param fileName 文件名
     * @return URL地址
     */
    public static String genPCSignURL(String docURI, String userId, String fileName) {
        ESealProperty property = ESealProperty.install();
        Map<String, String> headers = property.genHeaders();
        Map<String, String> paramMap = new HashMap<>();
        paramMap.put("docuri", docURI);
        paramMap.put("siteId", property.getSiteId());
        paramMap.put("typeId", userId);
        paramMap.put("type", "1");
        paramMap.put("documentName", fileName);
        paramMap.put("timestamp", StrUtil.toString(DateUtil.current()));
        paramMap.put("features", "eseal");
        paramMap.put("appkey", property.getAppKey());
        paramMap.put("random", headers.get("random"));
        paramMap.put("digest", headers.get("digest"));
        paramMap.put("host", property.getAppHost());
        String url = StrUtil.format(PC_SIGN_URL_TEMPLATE, paramMap);
        logInfo("生成PC端盖章链接：{}", url);
        return url;
    }

    /**
     * 上传文件
     * @param fileName 文件名
     * @param file 文件
     * @return 返回 uploadFileId
     */
    public net.sf.json.JSONObject upload(String userId, String fileName, File file) {
        if (this != FILE_UPLOAD) {
            throw new RuntimeException("非法调用");
        }
        if (StrUtil.isBlankIfStr(fileName)) {
            throw new RuntimeException("签章文件名不能为空");
        }
        if (null == file || !file.exists() || file.length() == 0) {
            throw new RuntimeException("签章文件不存在或者为0字节");
        }
        String suffix = FileUtil.getSuffix(fileName);
        if (!StrUtil.equalsIgnoreCase("pdf", suffix)) {
            logError("上传待签章文件失败,签章文件格式必须是PDF,当前格式是{}, userID={}, fileName={}", suffix, userId, fileName);
            throw new RuntimeException("签章文件格式必须是PDF，当前格式是：" + suffix);
        }
        ESealProperty property = ESealProperty.install();
        String apiUrl = property.getApiUrl(this);
        Map<String, String> headers = property.genHeaders();
        headers.put("x-tif-uuid", userId);
        HttpRequest request = HttpUtil.createRequest(Method.POST, apiUrl);
        request.form("file", file, fileName);
        request.headerMap(headers, true);
        request.timeout(this.getTimeout().intValue());
        try (HttpResponse response = request.execute()) {
            if (!response.isOk()) {
                uploadThrowError("上传待签章文件失败：网络状态码=" + response.getStatus(), "", userId, fileName, headers);
            }
            String body = response.body();
            if (!JSONUtil.isJsonObj(body)) {
                uploadThrowError("上传待签章文件返回内容格式不是JSONObject", body, userId, fileName, headers);
            }
            net.sf.json.JSONObject jsonObject = net.sf.json.JSONObject.fromObject(body);
            if (jsonObject.getInt("errcode") != 0) {
                uploadThrowError("上传待签章文件失败" + jsonObject.getString("errmsg"), body, userId, fileName, headers);
            }
            if (!jsonObject.containsKey("uploadFileId")) {
                uploadThrowError("上传待签章文件返回内容不包含关键字段<uploadFileId>", body, userId, fileName, headers);
            }
            String uploadFileId = jsonObject.getString("uploadFileId");
            if (StrUtil.isBlank(uploadFileId)) {
                uploadThrowError("上传待签章文件返回内容关键字段<uploadFileId>为空", body, userId, fileName, headers);
            }
            logInfo("上传待签章文件成功，userId={}, fileName={}, documentPath={}", userId, fileName, uploadFileId);
            return jsonObject;
        }
    }

    private void uploadThrowError(String errorMsg, String responseBody, String userId, String fileName, Map<String, String> headers) {
        String taskId = IdUtil.nanoId();
        logError("[{}]上传待签章文件，userID={}, fileName={}, headers={}", taskId, userId, fileName, JSONUtil.toJsonStr(headers));
        logError("[{}]上传待签章文件，返回结果：{}", taskId, responseBody);
        throw new RuntimeException(errorMsg);
    }

    /**
     * 下载文件
     * @param documentPath 文件地址
     */
    public ResultData<Object> download(String documentPath, String realPath, String pathPrefix) {
        if (this != FILE_DOWNLOAD) {
            throw new RuntimeException("非法调用");
        }
        if (StrUtil.isBlank(documentPath)) {
            throw new RuntimeException("下载签章文件路径不能为空");
        }
        if (StrUtil.isBlank(realPath)) {
            throw new RuntimeException("下载签章文件-保存路径为空");
        }
        ESealProperty property = ESealProperty.install();
        Map<String, String> headers = property.genHeaders();
        String apiUrl = property.getApiUrl(this);
        HttpRequest request = HttpUtil.createRequest(Method.POST, apiUrl);
        request.headerMap(headers, true);
        request.form("siteId", property.getSiteId());
        request.form("documentPath", documentPath);
        try (HttpResponse response = request.execute()) {
            if (!response.isOk()) {
                LOGGER.error(StrUtil.format("下载签章文件失败：documentPath={}，headers={}", documentPath, JSONUtil.toJsonStr(headers)));
                throw new RuntimeException("下载签章文件失败：网络状态码=" + response.getStatus());
            }
            String contentType = response.header("Content-Type");
            if (StrUtil.indexOfIgnoreCase(contentType, "application/json") > -1) {
                String body = response.body();
                LOGGER.error(StrUtil.format("下载签章文件失败：documentPath={}，headers={}， 返回值：{}", documentPath, JSONUtil.toJsonStr(headers), body));
                if (JSONUtil.isJsonObj(body)) {
                    throw new RuntimeException("下载签章文件失败：" + JSONUtil.parseObj(body).getStr("errorMsg", "无异常信息"));
                }
                throw new RuntimeException("下载签章文件失败：" + body);
            }
            if(StrUtil.endWith(realPath, StrPool.C_SLASH)){
                realPath = realPath.substring(0, realPath.length()-1);
            }
            String fileName = IdUtil.fastUUID() + ".pdf";
            String path = StringUtils.defaultIfBlank(pathPrefix, "")
                    .concat(StrPool.SLASH)
                    .concat(DateUtil.format(DateTime.now(), DatePattern.SIMPLE_MONTH_PATTERN))
                    .concat(StrPool.SLASH)
                    .concat(fileName);
            String filepath = realPath.concat(path);
            long length = response.writeBody(filepath);
            if (length == 0) {
                LOGGER.error(StrUtil.format("下载签章文件失败：下载的文件0字节，savePath={}，documentPath={}，headers={}", filepath, documentPath, JSONUtil.toJsonStr(headers)));
                throw new RuntimeException("下载签章文件0字节");
            }
            LOGGER.info(StrUtil.format("下载签章文件成功：documentPath={}，fileLength={}字节，savePath={}", length, filepath));
            Map<String, Object> rspData = new HashMap<>();
            rspData.put("filePath", path);
            rspData.put("fileName", fileName);
            rspData.put("fileLength", length);
            ResultData<Object> rd = ResultData.SUCCESS();
            rd.setData(rspData);
            return rd;
        }
    }

    private final String url;

    private final String describe;

    /**
     * 请求超时时间，单位秒
     */
    private final Long timeout;

    GDESealAPI(String url, String describe, Long timeout) {
        this.url = url;
        this.describe = describe;
        this.timeout = timeout;
    }

    public String getUrl() {
        return url;
    }

    public String getDescribe() {
        return describe;
    }

    public Long getTimeout() {
        return timeout;
    }

    @Override
    public String toString() {
        return describe;
    }

    private static final String MODULE_NAME = "省统一电子印章";

    private static void logInfo(String msg, Object...params) {
        if (LOGGER.isInfoEnabled()) {
            LOGGER.info(MODULE_NAME + "：" + StrUtil.format(msg, params));
        }
    }

    private static void logDebug(String msg, Object...params) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(MODULE_NAME + "：" + StrUtil.format(msg, params));
        }
    }

    private static void logError(String msg, Object...params) {
        LOGGER.error(MODULE_NAME + "：" + StrUtil.format(msg, params));
    }

    private static void logError(Throwable ex, String msg, Object...params) {
        LOGGER.error(MODULE_NAME + "：" + StrUtil.format(msg, params), ex);
    }

    private static class ESealProperty implements Serializable {

        private static final long serialVersionUID = 3037952340427387124L;

        private String appHost;

        private String appKey;

        private String appSerct;

        private String siteId;

        public static ESealProperty install() {
            ESealProperty property = new ESealProperty();
            property.setAppHost(SpringPropertyUtils.getPropertyValue("GDS_ESEAL_API_SERVER_URL"));
            property.setAppKey(SpringPropertyUtils.getPropertyValue("GDS_ESEAL_API_PAASID"));
            property.setAppSerct(SpringPropertyUtils.getPropertyValue("GDS_ESEAL_API_PAASTOKEN"));
            property.setSiteId(SpringPropertyUtils.getPropertyValue("GDS_ESEAL_API_SITEID"));
            return property;
        }

        /**
         * 获取完整的API地址
         * @param api API
         * @return String
         */
        public String getApiUrl(GDESealAPI api) {
            return StrUtil.format("{}{}", this.appHost, api.getUrl());
        }

        public Map<String, String> genHeaders() {
            String random = IdUtil.randomUUID();
            String digest = SmUtil.sm3(StrUtil.format("{}{}{}", this.appKey, this.appSerct, random));
            return MapBuilder.create(new HashMap<String, String>())
                    .put("appkey", this.appKey)
                    .put("random", random)
                    .put("digest", digest)
                    .build();
        }

        public String getAppHost() {
            return appHost;
        }

        public void setAppHost(String appHost) {
            this.appHost = appHost;
        }

        public String getAppKey() {
            return appKey;
        }

        public void setAppKey(String appKey) {
            this.appKey = appKey;
        }

        public String getAppSerct() {
            return appSerct;
        }

        public void setAppSerct(String appSerct) {
            this.appSerct = appSerct;
        }

        public String getSiteId() {
            return siteId;
        }

        public void setSiteId(String siteId) {
            this.siteId = siteId;
        }
    }
}
